import os
import sys
import re
import string
import shutil
from xml.sax import saxutils, parse, handler
from xml.dom import minidom
from datetime import timedelta, datetime
from dateutil import parser
from django.contrib.gis.geos import LineString, Point
from django.contrib.gis.geos.collections import MultiLineString
from models import MapTag, TrackPoint, Route, MapObject, Photo

def exceltime_to_datetime(exceltime):
    '''Convert MS Excel time format to datetime'''
    td = timedelta(days=exceltime)
    return datetime(1899, 12, 30) + td #Excel bug treats 1900 as leapyear, so use 12/30 as base


class ExcelHandler(handler.ContentHandler):
    def __init__(self):
        self.chars=[]
        self.cells=[]
        self.rows=[]
        self.tables=[]
    
    def characters(self, content):
        self.chars.append(content)
    
    def startElement(self, name, atts):
        if name=="Cell":
            self.chars=[]
        elif name=="Row":
            self.cells=[]
        elif name=="Table":
            self.rows=[]
    
    def endElement(self, name):
        if name=="Cell":
            self.cells.append(''.join(self.chars))
        elif name=="Row":
            self.rows.append(self.cells)
        elif name=="Table":
            self.tables.append(self.rows)


def createTrackPointPhotosFromExcelXml(filename, map_tag):
    '''Load Hig's special Photo Excel XML format and save a bunch of TrackPoints'''
    exh = ExcelHandler()
    parse(filename, exh)

    try:
        tag = MapTag.objects.get(value__iexact=map_tag)
    except:
        tag = MapTag.objects.create(value=map_tag)

    #compile my regexp early, to find the image_id from the filenemae
    #^\D+ matches all non-digits at the beginning
    #(?P<ddd>\d+) matches one ore more digits and assigns to group 'ddd
    reg = re.compile(r'^\D*(?P<ddd>\d+)')

    # Read each row, and save...
    for row in exh.tables[0][1:]:
        # Copy image to the right location...
        # Construct orig_fn, desg_fn, tag_fn
        orig_fn = row[1]
        filehead, fn = string.rsplit(orig_fn, u'\\', 1)
        dest_fn = os.path.join(u'/var/www/media/Images/Fullres', fn)
        tag_fn = os.path.join(u'Images/Fullres', fn)
        # debug since I don't have Hig's data... TODO: Remove the following line...
        orig_fn = u'/var/www/media/Images/Fullres/DSC_6100.jpg'
        # Copy image to target folder
        shutil.copy(orig_fn, dest_fn)
        m = reg.match(fn)
        tp = TrackPoint.objects.create(
            time=exceltime_to_datetime(float(row[0])),
            time_trusted=True,
            location=Point(0, 0, None),
            location_trusted=False,
            image = tag_fn,
            image_id = int(m.group('ddd')))
        tp.tags.add(tag)
        tp.save()


def createTrackPointGpsFromExcelXml(filename, map_tag):
    '''Load Hig's special GPS Excel XML format and save a bunch of TrackPoints'''
    exh = ExcelHandler()
    parse(filename, exh)
    latsign = 1.0
    lonsign = 1.0
    
    try:
        tag = MapTag.objects.get(value__iexact=map_tag)
    except:
        tag = MapTag.objects.create(value=map_tag)

    if (exh.tables[0][0][1].upper().startswith(u'S')):
        latsign = -1.0
    
    if (exh.tables[0][0][2].upper().startswith(u'W')):
        lonsign = -1.0

    # Read each row, and save...
    for row in exh.tables[0][1:]:
        tp = TrackPoint.objects.create(
            time=exceltime_to_datetime(float(row[0])),
            time_trusted=True,
            location=Point(lonsign * float(row[2]), latsign * float(row[1]), None),
            location_trusted=True)
        tp.tags.add(tag)
        tp.save()

def MultiLineStringFromTrackPoints(trackPoints, simplify_tolerance=0):
    pts = []
    for tp in trackPoints:
        if tp.location_trusted:
            pts.append(tp.location)
    ls = LineString(pts)
    return MultiLineString(ls.simplify(simplify_tolerance))

def trackPointsToRoute(map_tag, route_name=None, route_caption=None, simplify_tolerance=0):
    '''Load a bunch of TrackPoints and create a route'''
    if route_name == None:
        route_name = str(map_tag)
    tag = MapTag.objects.get(value=map_tag)
    if route_caption == None:
        route_caption = "Route created from all TrackPoints tagged with '" + str(map_tag) + "'"
    try: # Load all points that are tagged with the route name.
        loadTag = MapTag.objects.get(value=route_name)
    except: # Fall back to all points that are tagged with the map_tag
        loadTag = MapTag.objects.get(value=map_tag)
    #Create a LineString from the points
    mls = MultiLineStringFromTrackPoints(
        loadTag.trackpoint_set.all().order_by('time'),
        simplify_tolerance)
    r = Route.objects.create(
        name=route_name,
        caption=route_caption,
        track = mls)
    #r.owner = 1 #TODO: Fix This Eventually
    r.tags.add(tag)
    r.save()

def trackPointToPhoto(trackPoint):
    if (None != trackPoint.image):
        n = os.path.basename(trackPoint.image.name)
        imgName, nnn = string.rsplit(n, u'.', 1)
        p = Photo.objects.create(
            pk = trackPoint.image_id,
            image = trackPoint.image,
            location = trackPoint.location,
            timetaken = trackPoint.time,
            name = imgName,
            caption = "",
            long_caption = "",
            user = trackPoint.user
            )
        for t in trackPoint.tags.all():
            p.tags.add(t)
        p.save()

def iterMultiplier(t1, t2, t3):
    """ Returns the percentage td2 is between td1 and td3 with 1 second accuracy.  May overflow. """
    td1 = t2-t1
    td2 = t3-t1
    f1 = (td1.days * 86400) + td1.seconds + (td1.microseconds / 1000000.0)
    f2 = (td2.days * 86400) + td2.seconds + (td2.microseconds / 1000000.0)
    return f1/f2

def trackPointsToPhotos(map_tag):
    """ Load TrackPoints - interpolate location of photos, and create Photo models. """
    tag = MapTag.objects.get(value=map_tag)
    tpList = list(tag.trackpoint_set.all().order_by('time'))
    max = len(tpList)
    p1 = 0
    p2 = 0
    pm = 0

    #Start Case - photos taken before 1st GPS point.
    while (pm < max) and (False == tpList[pm].location_trusted):
        pm += 1
    if (pm > 0) and (True == tpList[pm].location_trusted):
        #Set each to the location in p1
        for i in tpList[:pm]:
            i.location = tpList[pm].location
            i.save()
            if None != i.image:
                trackPointToPhoto(i)
    
    #now the main algorithm...
    #walk pm until it hits an image without location...
    while (pm < max):
        while (pm < max) and (True == tpList[pm].location_trusted):
            pm += 1
        if (pm < max) and (pm > 0) and (False == tpList[pm].location_trusted):
            #set p1 to the GPS point before the image
            p1 = pm - 1
            #walk p2 to the 1st GPS point after the images
            p2 = pm + 1
            while (p2 < max) and (False == tpList[p2].location_trusted):
                p2 += 1
            if (p2 < max):
                # for each pm between p1 and p2 - interpolate a location
                while (pm < p2):
                    f = iterMultiplier(tpList[p1].time, tpList[pm].time, tpList[p2].time)
                    #Update Location
                    tpList[pm].location.x = tpList[p1].location.x + (f*(tpList[p2].location.x - tpList[p1].location.x))
                    tpList[pm].location.y = tpList[p1].location.y + (f*(tpList[p2].location.y - tpList[p1].location.y))
                    if (None != tpList[p1].location.z) and (None != tpList[p2].location.z):
                        tpList[pm].location.z = tpList[p1].location.z + (f*(tpList[p2].location.z - tpList[p1].location.z))
                    #Save TrackPoint & Convert To Photo
                    tpList[pm].save()
                    trackPointToPhoto(tpList[pm])
                    pm += 1
            else:
                # these photos are all at the end...  set all their location to the p1 location
                while (pm < max):
                    tpList[pm].location = tpList[p1].location
                    #Save TrackPoint & Convert To Photo
                    tpList[pm].save()
                    trackPointToPhoto(tpList[pm])
                    pm += 1
            p1 = pm = p2
    #LOOP


def loadXmlGps(filename, map_tag, route_name=None, route_caption=None):
    """ Creates a Route from an XML file of GPS Points """
    print "(%s) Loading Excel XML format..." % str(datetime.now())
    createTrackPointGpsFromExcelXml(filename, map_tag)
    print "(%s) Creating Route (0)..." % str(datetime.now())
    trackPointsToRoute(str(map_tag), route_name + " - original", route_caption, 0.0)
    print "(%s) Creating Route (5)..." % str(datetime.now())
    trackPointsToRoute(str(map_tag), route_name + " - 5", route_caption + " simplified with tolerance 5", 5.0)
    print "(%s) Creating Route (1)..." % str(datetime.now())
    trackPointsToRoute(str(map_tag), route_name + " - 1", route_caption+ " simplified with tolerance 1", 1.0)
    print "(%s) Creating Route (.5)..." % str(datetime.now())
    trackPointsToRoute(str(map_tag), route_name + " - 0.5", route_caption+ " simplified with tolerance 0.5", 0.5)
    print "(%s) Creating Route (.1)..." % str(datetime.now())
    trackPointsToRoute(str(map_tag), route_name + " - 0.1", route_caption+ " simplified with tolerance 0.1", 0.1)
    print "(%s) Creating Route (.05)..." % str(datetime.now())
    trackPointsToRoute(str(map_tag), route_name + " - 0.05", route_caption+ " simplified with tolerance 0.05", 0.05)
    print "(%s) Creating Route (.01)..." % str(datetime.now())
    trackPointsToRoute(str(map_tag), route_name + " - 0.01", route_caption+ " simplified with tolerance 0.01", 0.01)
    print "(%s) Complete!" % str(datetime.now())

def loadXmlPhotos(filename, map_tag):
    print "(%s) Creating trackPoints (with photos) from Excel XML format..." % str(datetime.now())
    createTrackPointPhotosFromExcelXml(filename, map_tag)
    print "(%s) Interpolating Location and Creating Photo Models..." % str(datetime.now())
    trackPointsToPhotos(str(map_tag))

def loadSmall():
    loadXmlGps('/data/GpsDataSmall.xml', 'Trip Subset', 'Short Trip', 'Shorter Trip... first few points only')
    loadXmlPhotos('/data/PhotosDataSmall.xml', 'Trip Subset')

def loadBig():
    loadXmlGps('/data/trip.xml', 'SeattleToAlaska', 'Full Trip', 'The Full Trip')


class Gpx:
    def parseTrack(trk, map_tag, route_caption=None):
        tag = MapTag.objects.get(value=map_tag)
        name = trk.getElementsByTagName('name')[0].firstChild.data
        try:
            name_tag = MapTag.objects.get(value=name)
        except:
            name_tag = MapTag.objects.create()
            name_tag.value = name
            name_tag.save()
        for trkseg in trk.getElementsByTagName('trkseg'):
            for trkpt in trkseg.getElementsByTagName('trkpt'):
                lat = float(trkpt.getAttribute('lat'))
                lon = float(trkpt.getAttribute('lon'))
                ele = float(trkpt.getElementsByTagName('ele')[0].firstChild.data)
                rfc3339 = trkpt.getElementsByTagName('time')[0].firstChild.data
                datetime = parser.parse(rfc3339)
                tp = TrackPoint.objects.create(
                    time=datetime,
                    time_trusted=True,
                    location=Point(lon, lat, ele),
                    location_trusted=True)
                tp.tags.add(tag)
                tp.tags.add(name_tag)
                tp.save()
        trackPointsToRoute(map_tag, name, route_caption)
    
    # Note: Route name is taken from the trk name in the GPX file...
    def loadRouteFromFile(filename, map_tag, route_caption=None):
        #Look Here: http://the.taoofmac.com/space/blog/2005/10/11/2359
        doc = minidom.parse(filename)
        doc.normalize()
        gpx = doc.documentElement
        for node in gpx.getElementsByTagName('trk'):
            parseTrack(node, map_tag, route_caption)
    
    @staticmethod
    def createTrackPointsFromTrk(trk, tags):
        tps = [];
        for trkseg in trk.getElementsByTagName('trkseg'):
            for trkpt in trkseg.getElementsByTagName('trkpt'):
                lat = float(trkpt.getAttribute('lat'))
                lon = float(trkpt.getAttribute('lon'))
                ele = float(trkpt.getElementsByTagName('ele')[0].firstChild.data)
                rfc3339 = trkpt.getElementsByTagName('time')[0].firstChild.data
                datetime = parser.parse(rfc3339)
                tp = TrackPoint.objects.create(
                    time=datetime,
                    time_trusted=True,
                    location=Point(lon, lat, ele),
                    location_trusted=True)
                for tag in tags:
                    tp.tags.add(tag)
                tp.save()
                tps.append(tp)
        return tps
    
    @staticmethod
    def CreateMultiLineString(filename, tags):
        doc = minidom.parse(filename)
        doc.normalize()
        gpx = doc.documentElement
        trk = gpx.getElementsByTagName('trk')[0]
        tps = Gpx.createTrackPointsFromTrk(trk, tags)
        return MultiLineStringFromTrackPoints(tps, 0)
